// eee.Sheffield.PZ.Imaging
//
// Copyright ?Ping Zou, 2007
// sg71.cherub@gmail.com

using System;
using System.Collections.Generic;
using System.Text;
using eee.Sheffield.PZ.Math;
using System.Drawing;
using System.Drawing.Imaging;

namespace eee.Sheffield.PZ.Imaging.ImageFilter
{
    public class EvaluateThinningByManualSegmentation
    {
        #region Fields
        // sources
        private string _resultImageFileName = "";
        private string _manualImageFileName = "";
        private Bitmap _resultImage = null;
        private Bitmap _manualImage = null;

        private int _tolerance;

        // Lref, Lf, Lo, A
        // refer to Lacoste 2005
        private int _Lref = 0;  // length of manual cetre lines
        private int _Lf = 0;    // length of false detections
        private int _Lo = 0;    // length of omisions
        // private int _A = 0;     // area between matched sections

        //// true positive / true negative / false positive / false negative
        //private int _truePositive = 0;
        //private int _trueNegative = 0;
        //private int _falsePositive = 0;
        //private int _falseNegative = 0;
        //private int _true = 0;
        //private int _false = 0;
        //private int _positive = 0;
        //private int _negative = 0;        
        #endregion

        #region Properties
        public double F { get { return 100 * (double)_Lf / (double)_Lref; } }
        public double O { get { return 100 * (double)_Lo / (double)_Lref; } }
        // TODO: public double D { get { return (double)_A / (double)(_Lref - _Lo); } }
        public string FValue { get { return String.Format("F: {0: 0.00}%", this.F); } }
        public string OValue { get { return String.Format("O: {0: 0.00}%", this.O); } }
        //public double TruePositive { get { return (double)_truePositive / (double)_positive; } }
        //public double FalsePositive { get { return (double)_falsePositive / (double)_positive; } }
        //public double FalseNegative { get { return (double)_falseNegative / (double)_negative; } }
        //public double TrueNegative { get { return (double)_trueNegative / (double)_negative; } }
        //public string StrTruePositive { get { return "True Positive Rate:" + this.TruePositive.ToString(); } }
        //public string StrTrueNegative { get { return "True Negative Rate:" + this.TrueNegative.ToString(); } }
        //public string StrFalsePositive { get {return "False Positive Rate (Type I Error):" + this.FalsePositive.ToString(); } }
        //public string StrFalseNegative { get { return "False Negative Rate (Type II Error):" + this.FalseNegative.ToString(); } }
        #endregion

        #region Contructor
        public EvaluateThinningByManualSegmentation(string result, string manual) : this(result, manual, 2) { }
        public EvaluateThinningByManualSegmentation(string result, string manual, int tolerance)
        {
            _resultImageFileName = result;
            _manualImageFileName = manual;
            _tolerance = tolerance;
        }
        public EvaluateThinningByManualSegmentation(Bitmap result, Bitmap manual) : this(result, manual, 2) { }
        public EvaluateThinningByManualSegmentation(Bitmap result, Bitmap manual, int tolerance)
        {
            _resultImage = new Bitmap(result);
            _manualImage = new Bitmap(manual);
            _tolerance = tolerance;
        } 
        #endregion

        /// <summary>
        /// evaluate thinning result
        /// </summary>
        public void EvaluateResult()
        {
            if (_resultImage == null || _manualImage == null)
            {
                // load result image and maual image
                _resultImage = new Bitmap(_resultImageFileName);
                _manualImage = new Bitmap(_manualImageFileName);
            }

            if (_resultImage.Height != _manualImage.Height
                || _resultImage.Width != _manualImage.Width)
                throw new ApplicationException("EvaluateThinningByManualSegmentation::EvaluateResult(), result and manual image do not match in dimension.");
            
            if (_resultImage.PixelFormat != PixelFormat.Format8bppIndexed)
                eee.Sheffield.PZ.Imaging.Image.StandardizeImageTo8bbpIndexed(ref _resultImage);

            if (_manualImage.PixelFormat != PixelFormat.Format8bppIndexed)
                eee.Sheffield.PZ.Imaging.Image.StandardizeImageTo8bbpIndexed(ref _manualImage);

            ConvertBitmapMatrix converter = new ConvertBitmapMatrix();
            PZMath_matrix resultMatrix = converter.ConvertGraylevelBitmapToMatrix(_resultImage);
            PZMath_matrix manualMatrix = converter.ConvertGraylevelBitmapToMatrix(_manualImage);
            bool[,] resultBoolMatrix = resultMatrix.ConvertToBoolMatrix(125);
            bool[,] manualBoolMatrix = manualMatrix.ConvertToBoolMatrix(125);

            // tolerance matrix
            bool[,] toleranceBoolMatrix = GetToleranceBoolMatrix(manualBoolMatrix, _tolerance);

            int width = _resultImage.Width;
            int height = _resultImage.Height;

            //PZMath_matrix resultTempMatrix = new PZMath_matrix(resultBoolMatrix);
            //Bitmap resultTempImage = converter.ConvertMatrixToGraylevelBitmap(resultTempMatrix);
            //resultTempImage.Save("result temp.bmp");

            //PZMath_matrix manualTempMatrix = new PZMath_matrix(manualBoolMatrix);
            //Bitmap manualTempImage = converter.ConvertMatrixToGraylevelBitmap(manualTempMatrix);
            //manualTempImage.Save("manual temp.bmp");

            //PZMath_matrix toleranceTempMatrix = new PZMath_matrix(toleranceBoolMatrix);
            //Bitmap toleranceTempImage = converter.ConvertMatrixToGraylevelBitmap(toleranceTempMatrix);
            //toleranceTempImage.Save("tolerance temp.bmp");

            // Lref
            for (int x = 0; x < width; x++)
                for (int y = 0; y < height; y++)
                    if (manualBoolMatrix[x, y])
                        _Lref++;

            // Lf & Lo
            bool[,] temp1 = new bool[width, height];
            bool[,] temp2 = new bool[width, height];
            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    temp1[x, y] = resultBoolMatrix[x, y];
                    temp2[x, y] = manualBoolMatrix[x, y];
                    
                    // Lf
                    if (resultBoolMatrix[x, y] && toleranceBoolMatrix[x, y])
                        temp1[x, y] = false;
                    if (temp1[x, y])
                        _Lf++;
                    
                    // Lo
                    if (manualBoolMatrix[x, y])
                    {
                        // search in tolerance region
                        if (IsInTolerance(resultBoolMatrix, x, y, _tolerance))
                        {
                            temp2[x, y] = false;
                        }
                    }
                    if (temp2[x, y])
                        _Lo++;
                }
            }


            //// Lo
            //bool[,] temp2 = new bool[width, height];
            //for (int x = 0; x < width; x++)
            //{
            //    for (int y = 0; y < height; y++)
            //    {
            //        temp2[x, y] = manualBoolMatrix[x, y];
            //        if (manualBoolMatrix[x, y])
            //        {
            //            // search in tolerance region
            //            if (IsInTolerance(resultBoolMatrix, x, y, _tolerance))
            //            {
            //                temp2[x, y] = false;
            //            }
            //        }
            //        if (temp2[x, y])
            //            _Lo++;
            //    }
            //}

            // TODO: A included area between result and manual         
        } // EvaluateResult()

        /// <summary>
        /// check if there existing result in the tolerance region
        /// </summary>
        private bool IsInTolerance(bool[,] resultBoolMatrix, int x, int y, int tolerance)
        {
            bool isIn = false;

            int kStart = -1 * tolerance;
            int kEnd = tolerance;
            int width = resultBoolMatrix.GetLength(0);
            int height = resultBoolMatrix.GetLength(1);

            for (int kx = kStart; kx <= kEnd; kx++)
            {
                for (int ky = kStart; ky <= kEnd; ky++)
                {
                    int lx = x + kx;
                    int ly = y + ky;
                    if (lx >= 0 && lx < width && ly >= 0 && ly < height)
                    {
                        if (resultBoolMatrix[lx, ly])
                        {
                            isIn = true;
                            return isIn;
                        }
                    }
                }
            }

            return isIn;
        } // IsInTolerance()

        /// <summary>
        /// get tolerance matrix by fill around of centre lines
        /// </summary>
        /// <param name="manualBoolMatrix"></param>
        /// <param name="_tolerance"></param>
        /// <returns></returns>
        private bool[,] GetToleranceBoolMatrix(bool[,] manualBoolMatrix, int tolerance)
        {
            int width = manualBoolMatrix.GetLength(0);
            int height = manualBoolMatrix.GetLength(1);
            bool[,] dstBoolMatrix = new bool[width, height];

            int kStart = -1 * tolerance;
            int kEnd = tolerance;
            int lx;
            int ly;
            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    dstBoolMatrix[x, y] = manualBoolMatrix[x, y];
                    if (manualBoolMatrix[x, y])
                    // fill around
                    {
                        for (int kx = kStart; kx <= kEnd; kx++)
                        {
                            for (int ky = kStart; ky <= kEnd; ky++)
                            {
                                lx = x + kx;
                                ly = y + ky;
                                if (lx >= 0 && lx < width && ly >= 0 && ly < height)
                                    dstBoolMatrix[lx, ly] = manualBoolMatrix[x, y];
                            }
                        }
                    }
                }
            }

            return dstBoolMatrix;
        } // EvaluateResult()


    }
}
